import fs from 'node:fs'
import path from 'node:path'

import { BaseLoader, UnfilteredLoaderChunk } from '@cherrystudio/embedjs-interfaces'
import createDebugMessages from 'debug'
import md5 from 'md5'
import { getMimeType } from 'stream-mime-type'

import { createLoaderFromMimeType } from '../util/mime.js'

/**
 * @class LocalPathLoader
 * @extends BaseLoader
 * @description 本地路径加载器，用于从本地文件系统加载文件或目录内容。
 * 它继承自 BaseLoader，并实现了加载本地路径内容的逻辑。
 */
export class LocalPathLoader extends BaseLoader<{ type: 'LocalPathLoader' }> {
  // 调试消息生成器
  private readonly debug = createDebugMessages('embedjs:loader:LocalPathLoader')
  // 要加载的本地文件或目录路径
  private readonly path: string
  // 分块策略
  private readonly selectedStrategy: string
  private readonly semanticModel?: string
  private readonly semanticdimensions?: number
  private readonly semanticApiKey?: string
  private readonly semanticapiVersion?: string
  private readonly semanticapiBaseURL?: string

  /**
   * @constructor
   * @param {object} params - 构造函数参数
   * @param {string} params.path - 要加载的本地文件或目录路径
   * @param {number} [params.chunkSize] - 分块大小，可选，默认为 500
   * @param {number} [params.chunkOverlap] - 分块重叠大小，可选，默认为 0
   * @param {string} params.selectedStrategy - 分块策略
   * @description 构造函数，初始化 LocalPathLoader 实例。
   * 调用父类构造函数并设置加载器的唯一 ID、路径、分块参数和分块策略。
   */
  constructor({
    path,
    chunkSize,
    chunkOverlap,
    selectedStrategy,
    semanticModel,
    semanticdimensions,
    semanticApiKey,
    semanticapiVersion,
    semanticapiBaseURL
  }: {
    path: string
    chunkSize?: number
    chunkOverlap?: number
    selectedStrategy: string
    semanticModel?: string
    semanticdimensions?: number
    semanticApiKey?: string
    semanticapiVersion?: string
    semanticapiBaseURL?: string
  }) {
    // 调用父类构造函数，设置加载器名称、元数据、分块大小、重叠和策略
    console.log('LocalPathLoader', path, chunkSize, chunkOverlap, selectedStrategy)
    super(`LocalPathLoader_${md5(path)}`, { path }, chunkSize ?? 500, chunkOverlap ?? 0, selectedStrategy ?? 'fixed')
    // 保存本地路径
    this.path = path
    this.selectedStrategy = selectedStrategy ?? 'fixed'
    this.semanticModel = semanticModel
    this.semanticdimensions = semanticdimensions
    this.semanticApiKey = semanticApiKey
    this.semanticapiVersion = semanticapiVersion
    this.semanticapiBaseURL = semanticapiBaseURL
    // 设置分块策略
    //this.splitStrategy = selectedStrategy
  }

  /**
   * @method getUnfilteredChunks
   * @async
   * @generator
   * @returns {AsyncGenerator<UnfilteredLoaderChunk, void, void>}
   * @description 获取未过滤的文本块。
   * 递归地处理指定的路径，并生成 UnfilteredLoaderChunk 文本块。
   */
  override async *getUnfilteredChunks() {
    // 递归处理路径并生成文本块
    for await (const result of await this.recursivelyAddPath(this.path)) {
      yield {
        ...result,
        metadata: {
          ...result.metadata,
          type: <const>'LocalPathLoader',
          originalPath: this.path
        }
      }
    }
  }

  /**
   * @method recursivelyAddPath
   * @private
   * @async
   * @generator
   * @param {string} currentPath - 当前要处理的路径
   * @returns {AsyncGenerator<UnfilteredLoaderChunk, void, void>}
   * @description 递归地添加文件或目录路径并生成文本块。
   * 如果是文件，则根据 MIME 类型创建相应的加载器并加载内容；
   * 如果是目录，则递归处理目录中的所有文件和子目录。
   */
  private async *recursivelyAddPath(currentPath: string): AsyncGenerator<UnfilteredLoaderChunk, void, void> {
    // 检查当前路径是目录还是文件
    const isDir = fs.lstatSync(currentPath).isDirectory()
    this.debug(`Processing path '${currentPath}'. It is a ${isDir ? 'Directory!' : 'file...'}`)

    // 如果是文件
    if (!isDir) {
      // 创建文件读取流
      const stream = fs.createReadStream(currentPath)
      // 获取文件的 MIME 类型
      let { mime } = await getMimeType(stream)
      // 关闭流
      stream.destroy()

      this.debug(`File '${this.path}' has mime type '${mime}'`)
      // 处理特殊 MIME 类型，例如 application/octet-stream
      if (mime === 'application/octet-stream') {
        // 根据文件扩展名尝试确定更准确的 MIME 类型
        const extension = currentPath.split('.').pop()?.toLowerCase() ?? ''
        if (extension === 'md' || extension === 'mdx') {
          mime = 'text/markdown'
        }
        if (extension === 'txt') {
          mime = 'text/plain'
        }
        this.debug(`File '${this.path}' mime type updated to 'text/markdown'`)
      }

      try {
        // 根据 MIME 类型创建相应的加载器
        console.log(
          'createLoaderFromMimeType',
          currentPath,
          mime,
          this.chunkSize,
          this.chunkOverlap,
          this.selectedStrategy
        )
        const loader = await createLoaderFromMimeType(
          currentPath,
          mime,
          this.chunkSize,
          this.chunkOverlap,
          this.selectedStrategy,
          this.semanticModel,
          this.semanticdimensions,
          this.semanticApiKey,
          this.semanticapiVersion,
          this.semanticapiBaseURL
        )
        // 使用创建的加载器获取文本块并生成
        for await (const result of await loader.getUnfilteredChunks()) {
          yield {
            pageContent: result.pageContent,
            metadata: {
              source: currentPath
            }
          }
        }
      } catch (err) {
        // 捕获创建加载器或加载过程中的错误
        this.debug(`Error creating loader for mime type '${mime}'`, err)
      }
    } else {
      // 如果是目录
      // 读取目录中的所有文件和子目录
      const files = fs.readdirSync(currentPath)
      this.debug(`Dir '${currentPath}' has ${files.length} entries inside`, files)

      // 递归处理目录中的每个条目
      for (const file of files) {
        for await (const result of await this.recursivelyAddPath(path.resolve(currentPath, file))) {
          yield result
        }
      }
    }
  }
}
